package scaladuct

import hint.HasHints
import util.logging.Logged

/**
 * <p>
 * Created by IntelliJ IDEA.
 * User: zslajchrt
 * Date: Jun 28, 2010
 * Time: 8:33:27 PM
 */

object ConfigPageGateway {
  def apply[X](firstPage: ConfigPage[X]) = new ConfigPageGateway(firstPage)
}

@serializable @SerialVersionUID(1L)
class FinishedPage[X](val page: ConfigPage[X], val result: ConfigResult[X]) {
  def canUnconsume(): Boolean = {
    page.canUnconsume(result)
  }

  def unconsume() = {
    page.unconsume(result)
  }
}

/**
 * This class may be enhanced by mixins like MementoRecordingGateway or ValidatingGateway.
 */
@serializable @SerialVersionUID(1L)
class ConfigPageGateway[X](firstPage: ConfigPage[X]) extends Logged {

  private[this] var cp: ConfigPage[X] = null
  private[this] var starter: Starter[X] = null
  private[this] var initialized: Boolean = false

  private[this] def currentPage: ConfigPage[X] = {
    if (!initialized) {
      initialized = true
      currentPage = firstPage
    }
    cp
  }

  private[this] def currentPage_=(page: ConfigPage[X]) = {
    if (page != null) {
      cp = decoratePage(page)
      info("Current page: " + id)
    } else {
      cp = null
      info("Page reset")
    }
  }



  private[this] var finishedPages: List[FinishedPage[X]] = Nil


  def id = if (currentPage == null)
    ""
  else
    currentPage.id

  def getCurrentPage() = currentPage

  def getStarter() = starter

  def getCookie(): AnyRef = if (currentPage != null)
    currentPage.getCookie
  else
    throw new IllegalStateException("Finished")

  def consumeCookie(cookie: AnyRef): Option[Starter[X]] = {
    val result: ConfigResult[X] = currentPage.consumeRawCookie(cookie)
    finishedPages ::= new FinishedPage(currentPage, result)
    result match {
      case NextPage(page) => {
        currentPage = page

        if (currentPage.getCookie == Terminal) {
          consumeCookie(Terminal) // short-circuit consumption
        } else None
      }
      case NewStarter(starter) => {
        currentPage = null
        this.starter = starter
        Some(starter)
      }
    }
  }

  protected def decoratePage(page: ConfigPage[X]): ConfigPage[X] = {
    page
  }

  def configure(moderator: (ConfigPage[X]) => AnyRef): Starter[X] = {
    consumeCookie(moderator(currentPage)) match {
      case Some(starter) => starter
      case None => configure(moderator)
    }
  }

  def configure(): Starter[X] = {
    configure((p) => {
      require(p.getCookie == Terminal, "Unexpected configuration page:" + p)
      p.getCookie
    })
  }

  def canUnconsume(): Boolean = {

    def canUnconsume(finished: List[FinishedPage[X]]): Boolean = {
      if (!finished.isEmpty && finished.head.canUnconsume)
        if (finished.head.page.getCookie == Terminal) {
          canUnconsume(finished.tail)
        } else true
      else
        false
    }

    canUnconsume(finishedPages)
  }

  def unconsume(): Unit = {
    assert(canUnconsume, "unable to roll back")
    if (currentPage != null)
      currentPage.cancel
    val lastFinished = finishedPages.head
    lastFinished.unconsume
    currentPage = lastFinished.page
    finishedPages = finishedPages.tail

    if (currentPage.getCookie == Terminal)
      unconsume() // short-circuit consumption

  }

  def close(): Unit = {
  }

  def pageExtension[E](extensionClass: Class[E]): Option[E] = {
    if (currentPage == null)
      None
    else
      currentPage.extension(extensionClass)
  }

}

trait GatewayWithHints[X] extends ConfigPageGateway[X] {
  private def hasHintsExt: HasHints[Any] = if (getCurrentPage() != null) {
    getCurrentPage.extension(classOf[HasHints[Any]]) match {
      case None => null
      case Some(ext) => ext
    }
  } else {
    null
  }

  def hints(subject: String): List[Property[Any]] = {
    hasHintsExt match {
      case null => Nil
      case h: HasHints[Any] => h.hints(subject).asInstanceOf[List[Property[Any]]]
    }
  }

  def hintPage(hint: Property[Any]): ConfigPage[Any] = hasHintsExt match {
    case null => throw new UnsupportedOperationException
    case h: HasHints[Any] => {
      h.hintPage(hint.asInstanceOf[Property[h.HintType]]).asInstanceOf[ConfigPage[Any]]
    }
  }
}